﻿// ---------------------------------------------------------------------------------------------------------------------
//   Copyright (c) SRT Solutions 2009. All rights reserved.
// ---------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.ComponentModel;

using NUnit.Framework;

using Moq;

namespace SRTSolutions.Elevate.Guide
{
    public class ObservableLists
    {
        #region Unit Tests

        [Test]
        public void ArgumentNull()
        {
            Assert.Throws<ArgumentNullException>(() => new ObservableList<string>(null));
        }

        #region Basic Operations

        [Test]
        public void Get()
        {
            var mock = new Mock<IList<int>>();
            mock.SetupGet(list => list[10]).Returns(165);

            var observable = new ObservableList<int>(mock.Object);
            Assert.AreEqual(165, observable[10]);
        }

        [Test]
        public void Set()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable[10] = 165;

            mock.VerifySet(list => list[10] = 165, Times.Once());
        }

        [Test]
        public void Add()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.Add(2001);

            mock.Verify(list => list.Add(2001), Times.Once());
        }

        [Test]
        public void Clear()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.Clear();

            mock.Verify(list => list.Clear(), Times.Once());
        }

        [Test]
        public void Contains()
        {
            var mock = new Mock<IList<int>>();
            mock.Setup(list => list.Contains(544)).Returns(true);

            var observable = new ObservableList<int>(mock.Object);

            Assert.IsTrue(observable.Contains(544));

            mock.Verify(list => list.Contains(544), Times.Once());
        }

        [Test]
        public void CopyTo()
        {
            var mock = new Mock<IList<int>>();

            int[] tmp = null;
            var observable = new ObservableList<int>(mock.Object);
            observable.CopyTo(tmp, 344);

            mock.Verify(list => list.CopyTo(tmp, 344), Times.Once());
        }

        [Test]
        public void GetEnumerator()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.GetEnumerator();

            mock.Verify(list => list.GetEnumerator(), Times.Once());
        }

        [Test]
        public void GetEnumeratorExplicit()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            ((IEnumerable)observable).GetEnumerator();

            mock.Verify(list => list.GetEnumerator(), Times.Once());
        }

        [Test]
        public void IndexOf()
        {
            var mock = new Mock<IList<int>>();
            mock.Setup(list => list.IndexOf(523)).Returns(941);

            var observable = new ObservableList<int>(mock.Object);

            var result = observable.IndexOf(523);

            Assert.AreEqual(941, result);
            mock.Verify(list => list.IndexOf(523), Times.Once());
        }

        [Test]
        public void Insert()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.Insert(100, 245);

            mock.Verify(list => list.Insert(100, 245), Times.Once());
        }

        [Test]
        public void IsReadOnly()
        {
            var mock = new Mock<IList<int>>();
            mock.SetupGet(list => list.IsReadOnly).Returns(true);

            var observable = new ObservableList<int>(mock.Object);

            Assert.IsTrue(observable.IsReadOnly);
            mock.VerifyGet(list => list.IsReadOnly, Times.Once());
        }

        [Test]
        public void Remove()
        {
            var list = GetObservableListWith0Through10();
            Assert.IsTrue(list.Remove(7));
            Assert.AreEqual(10, list.Count);
        }

        [Test]
        public void RemoveAt()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.RemoveAt(944);

            mock.Verify(list => list.RemoveAt(944), Times.Once());
        }

        #endregion

        #region Basic Operations With Observable Watcher

        //TODO: Refactor this to not duplicate code above
        [Test]
        public void GetWhileWatched()
        {
            var mock = new Mock<IList<int>>();
            mock.SetupGet(list => list[10]).Returns(165);

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;
            Assert.AreEqual(165, observable[10]);
        }

        [Test]
        public void SetWhileWatched()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;
            observable[10] = 165;

            mock.VerifySet(list => list[10] = 165, Times.Once());
        }

        [Test]
        public void AddWhileWatched()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;
            observable.Add(2001);

            mock.Verify(list => list.Add(2001), Times.Once());
        }

        [Test]
        public void ClearWhileWatched()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;
            observable.Clear();

            mock.Verify(list => list.Clear(), Times.Once());
        }

        [Test]
        public void ContainsWhileWatched()
        {
            var mock = new Mock<IList<int>>();
            mock.Setup(list => list.Contains(544)).Returns(true);

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;

            Assert.IsTrue(observable.Contains(544));

            mock.Verify(list => list.Contains(544), Times.Once());
        }

        [Test]
        public void CopyToWhileWatched()
        {
            var mock = new Mock<IList<int>>();

            int[] tmp = null;
            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;
            observable.CopyTo(tmp, 344);

            mock.Verify(list => list.CopyTo(tmp, 344), Times.Once());
        }

        [Test]
        public void GetEnumeratorWhileWatched()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;
            observable.GetEnumerator();

            mock.Verify(list => list.GetEnumerator(), Times.Once());
        }

        [Test]
        public void GetEnumeratorExplicitWhileWatched()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;
            ((IEnumerable)observable).GetEnumerator();

            mock.Verify(list => list.GetEnumerator(), Times.Once());
        }

        [Test]
        public void IndexOfWhileWatched()
        {
            var mock = new Mock<IList<int>>();
            mock.Setup(list => list.IndexOf(523)).Returns(941);

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;

            var result = observable.IndexOf(523);

            Assert.AreEqual(941, result);
            mock.Verify(list => list.IndexOf(523), Times.Once());
        }

        [Test]
        public void InsertWhileWatched()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;
            observable.Insert(100, 245);

            mock.Verify(list => list.Insert(100, 245), Times.Once());
        }

        [Test]
        public void IsReadOnlyWhileWatched()
        {
            var mock = new Mock<IList<int>>();
            mock.SetupGet(list => list.IsReadOnly).Returns(true);

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;

            Assert.IsTrue(observable.IsReadOnly);
            mock.VerifyGet(list => list.IsReadOnly, Times.Once());
        }

        [Test]
        public void RemoveWhileWatched()
        {
            var list = GetObservableListWith0Through10();
            list.CollectionChanged += ignore;
            Assert.IsTrue(list.Remove(7));
            Assert.AreEqual(10, list.Count);
        }

        [Test]
        public void RemoveAtWhileWatched()
        {
            var mock = new Mock<IList<int>>();

            var observable = new ObservableList<int>(mock.Object);
            observable.CollectionChanged += ignore;
            observable.RemoveAt(944);

            mock.Verify(list => list.RemoveAt(944), Times.Once());
        }

        void ignore(object sender, NotifyCollectionChangedEventArgs args)
        {
        }

        #endregion

        ObservableList<int> GetObservableListWith0Through10()
        {
            var values = 0.Through(10).ToList();
            return new ObservableList<int>(values);
        }

        #region PropertyChanged Event Tests

        class Results<T>
        {
            public Results(ObservableList<int> collection, T args, object sender, bool wasCalled)
            {
                this.Collection = collection;
                this.Args = args;
                this.Sender = sender;
                this.WasCalled = wasCalled;
            }

            public ObservableList<int> Collection { get; private set; }

            public T Args { get; private set; }

            public object Sender { get; private set; }

            public bool WasCalled { get; private set; }
        }

        Results<PropertyChangedEventArgs> GetPropertyChangedResults(Action<ObservableList<int>> action)
        {
            var collection = GetObservableListWith0Through10();
            INotifyPropertyChanged asPropertyChanged = collection;

            PropertyChangedEventArgs args = null;
            object actualSender = null;
            bool wasCalled = false;

            asPropertyChanged.PropertyChanged +=
                (sender, e) =>
                {
                    args = e;
                    actualSender = sender;
                    wasCalled = true;
                };

            action(collection);
            return new Results<PropertyChangedEventArgs>(collection, args, actualSender, wasCalled);
        }

        [Test]
        public void GetPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => { int tmp = collection[2]; });

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void SetPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => collection[4] = 10);

            Assert.AreEqual("Item[]", result.Args.PropertyName);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void SetBadIndexPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => Assert.Throws<ArgumentOutOfRangeException>(() => collection[-10] = 123));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void AddPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => collection.Add(0));

            Assert.AreEqual("Item[]", result.Args.PropertyName);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void ClearPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => collection.Clear());

            Assert.AreEqual("Item[]", result.Args.PropertyName);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void ContainsPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => collection.Contains(5));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void CopyToPropertyChanged()
        {
            var tmp = new int[11];
            var result = GetPropertyChangedResults(collection => collection.CopyTo(tmp, 0));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void GetEnumeratorPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => collection.GetEnumerator());

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void GetEnumeratorExplicitPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => ((IEnumerable) collection).GetEnumerator());

            Assert.IsFalse(result.WasCalled);
        }
        [Test]
        public void IndexOfPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => collection.IndexOf(10));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void InsertPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => collection.Insert(3, 15));

            Assert.AreEqual("Item[]", result.Args.PropertyName);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void InsertBadIndexPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => Assert.Throws<ArgumentOutOfRangeException>(() => collection.Insert(-1, 15)));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void RemovePropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => collection.Remove(5));

            Assert.AreEqual("Item[]", result.Args.PropertyName);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void RemoveBadItemPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => collection.Remove(-1));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void RemoveAtPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => collection.RemoveAt(5));

            Assert.AreEqual("Item[]", result.Args.PropertyName);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void RemoveAtBadIndexPropertyChanged()
        {
            var result = GetPropertyChangedResults(collection => Assert.Throws<ArgumentOutOfRangeException>(() => collection.RemoveAt(-1)));

            Assert.IsFalse(result.WasCalled);
        }

        #endregion

        #region CollectionChanged Event Tests

        Results<NotifyCollectionChangedEventArgs> GetCollectionChangedResults(Action<ObservableList<int>> action)
        {
            var collection = GetObservableListWith0Through10();

            NotifyCollectionChangedEventArgs args = null;
            object actualSender = null;
            bool wasCalled = false;

            collection.CollectionChanged +=
                (sender, e) =>
                {
                    args = e;
                    actualSender = sender;
                    wasCalled = true;
                };

            action(collection);

            return new Results<NotifyCollectionChangedEventArgs>(collection, args, actualSender, wasCalled);
        }

        [Test]
        public void GetCollectionChanged()
        {
            var results = GetCollectionChangedResults(collection => { var tmp = collection[2]; });
            Assert.IsFalse(results.WasCalled);
        }

        [Test]
        public void SetCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => collection[5] = 200);

            Assert.AreEqual(NotifyCollectionChangedAction.Replace, result.Args.Action);
            Assert.AreEqual(new[] { 200 }, result.Args.NewItems);
            Assert.AreEqual(5, result.Args.NewStartingIndex);
            Assert.AreEqual(new[] { 5 }, result.Args.OldItems);
            Assert.AreEqual(5, result.Args.OldStartingIndex);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void SetEqualValueCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => collection[5] = 5);

            Assert.AreEqual(NotifyCollectionChangedAction.Replace, result.Args.Action);
            Assert.AreEqual(new[] { 5 }, result.Args.NewItems);
            Assert.AreEqual(5, result.Args.NewStartingIndex);
            Assert.AreEqual(new[] { 5 }, result.Args.OldItems);
            Assert.AreEqual(5, result.Args.OldStartingIndex);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void SetBadIndexCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => Assert.Throws<ArgumentOutOfRangeException>(() => collection[-1] = 5));
            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void AddCollectionChanged()
        {
            var results = GetCollectionChangedResults(collection => collection.Add(1));

            Assert.AreEqual(NotifyCollectionChangedAction.Add, results.Args.Action);
            Assert.AreEqual(new[] { 1 }, results.Args.NewItems);
            Assert.AreEqual(11, results.Args.NewStartingIndex);
            Assert.IsNull(results.Args.OldItems);
            Assert.AreEqual(-1, results.Args.OldStartingIndex);
            Assert.AreSame(results.Collection, results.Sender);
        }

        [Test]
        public void ClearCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => collection.Clear());

            Assert.AreEqual(NotifyCollectionChangedAction.Reset, result.Args.Action);
            Assert.IsNull(result.Args.NewItems);
            Assert.AreEqual(-1, result.Args.NewStartingIndex);
            Assert.IsNull(result.Args.OldItems);
            Assert.AreEqual(-1, result.Args.OldStartingIndex);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void ContainsCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => collection.Contains(5));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void CopyToCollectionChanged()
        {
            var tmp = new int[11];
            var result = GetCollectionChangedResults(collection => collection.CopyTo(tmp, 0));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void GetEnumeratorCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => collection.GetEnumerator());

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void IndexOfCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => collection.IndexOf(4));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void InsertCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => collection.Insert(0, 15));

            Assert.AreEqual(NotifyCollectionChangedAction.Add, result.Args.Action);
            Assert.AreEqual(new[] { 15 }, result.Args.NewItems);
            Assert.AreEqual(0, result.Args.NewStartingIndex);
            Assert.IsNull(result.Args.OldItems);
            Assert.AreEqual(-1, result.Args.OldStartingIndex);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void InsertBadIndexCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => Assert.Throws<ArgumentOutOfRangeException>(() => collection.Insert(100, 15)));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void RemoveCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => collection.Remove(5));

            Assert.AreEqual(NotifyCollectionChangedAction.Remove, result.Args.Action);
            Assert.AreEqual(null, result.Args.NewItems);
            Assert.AreEqual(-1, result.Args.NewStartingIndex);
            Assert.AreEqual(new[] { 5 }, result.Args.OldItems);
            Assert.AreEqual(5, result.Args.OldStartingIndex);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void RemoveBadItemCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => collection.Remove(-1));

            Assert.IsFalse(result.WasCalled);
        }

        [Test]
        public void RemoveAtCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => collection.RemoveAt(2));

            Assert.AreEqual(NotifyCollectionChangedAction.Remove, result.Args.Action);
            Assert.AreEqual(null, result.Args.NewItems);
            Assert.AreEqual(-1, result.Args.NewStartingIndex);
            Assert.AreEqual(new[] { 2 }, result.Args.OldItems);
            Assert.AreEqual(2, result.Args.OldStartingIndex);
            Assert.AreSame(result.Collection, result.Sender);
        }

        [Test]
        public void RemoveAtBadIndexCollectionChanged()
        {
            var result = GetCollectionChangedResults(collection => Assert.Throws<ArgumentOutOfRangeException>(() => collection.RemoveAt(50)));

            Assert.IsFalse(result.WasCalled);
        }

        #endregion

        #endregion
    }
}
